/*
 * lock.c
 *
 */

#include "lock.h"
#include "libdlm.h"
#include "log.h"

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

lock_restriction_t lock_restrictions[DLM_lock_count];
lock_mask_t lock_mask[DLM_lock_count];

static struct process *proc_root;
static struct resource *res_root;
static struct lock *waiting_root;
// lista dodanych blokad po usunieciu jakiejs blokady
static struct lock *added_root;

static int add_conflict_lock(struct lock *lock, struct lock *conflict);
static int add_waiting_lock(struct lock *lock);
static void rm_waiting_lock(struct lock *lock);
/*
 * inicjalizacja struktury danych
 */
void lock_init(void)
{
	/* initialize structure */
	lock_mask[DLM_lock_CR] = 0x80;			/* 1000 0000 */
	lock_mask[DLM_lock_CW] = 0x40;			/* 0100 0000 */
	lock_mask[DLM_lock_PR] = 0x20;			/* 0010 0000 */
	lock_mask[DLM_lock_PW] = 0x10;			/* 0001 0000 */
	lock_mask[DLM_lock_Ex] = 0x08;			/* 0000 1000 */

	lock_restrictions[DLM_lock_CR] = 0xf0;		/* 1111 0000 */
	lock_restrictions[DLM_lock_CW] = 0xc0;		/* 1100 0000 */
	lock_restrictions[DLM_lock_PR] = 0xa0;		/* 1010 0000 */
	lock_restrictions[DLM_lock_PW] = 0x80;		/* 1000 0000 */
	lock_restrictions[DLM_lock_Ex] = 0x00;		/* 0000 0000 */

	proc_root = NULL;
	res_root = NULL;
	waiting_root = NULL;
}
/*
 * funcja znajduje wskaznik na proces o podanym pidzie
 */
struct process* lock_ck_process(pid_t pid)
{
    //TODO 1.zablokuj listę procesów - można odczytywać, nie można edytować
    //2.wyszukaj proces o podanym pid
    struct process *tmp_proc = proc_root;
    if(tmp_proc->pid == pid)
        return tmp_proc;
    while(tmp_proc->next != NULL){
        tmp_proc = tmp_proc->next;
        if(tmp_proc->pid == pid)
            return tmp_proc;
    }
    return NULL;
    //TODO 3.odblokuj zasob
    //4.znalazlem - zwroc, nie - NULL
}
/*
 * funkcja znajduje zasob dla struktury stat
 */
struct resource* lock_ck_resource_stat(struct stat* st)
{
    //TODO 1.zablokuj listę zasobów - można odczytywać, nie można edytować
    //2.wyszukaj zasob o podanych parametrac
    struct resource *tmp_res = res_root;
    if(tmp_res->st_dev == st->st_dev
       && tmp_res->st_ino == st->st_ino
       && tmp_res->st_rdev == st->st_rdev)
        return tmp_res;
    while(tmp_res->next != NULL){
        tmp_res = tmp_res->next;
        if(tmp_res->st_dev == st->st_dev
           && tmp_res->st_ino == st->st_ino
           && tmp_res->st_rdev == st->st_rdev)
            return tmp_res;
    }
    return NULL;
    //TODO 3.odblokuj zasob
    //4.znalazlem - zwroc, nie - NULL
}
/*
 * funkcja zwraca wskaznik na zasob o podanym identyfikatorze
 */
struct resource* lock_ck_resource(resource_id_t resid)
{
    //TODO 1.zablokuj listę zasobów - można odczytywać, nie można edytować
    //2.wyszukaj zasob o podanych parametrac
    struct resource *tmp_res = res_root;
    if(tmp_res->id == resid)
        return tmp_res;
    while(tmp_res->next != NULL){
        tmp_res = tmp_res->next;
        if(tmp_res->id == resid)
            return tmp_res;
    }
    return NULL;
    //TODO 3.odblokuj zasob
    //4.znalazlem - zwroc, nie - NULL
}

struct lock* lock_ck_lock(struct resource *res, struct process *proc)
{
	struct lock *lck = res->locks;
	while (lck != NULL) {
		if (lck->proc == proc)
			return lck;
		lck = lck->next;
	}
	return NULL;
}
/*
 * funkcja dodaje nowy proces o konkretnym pidzie
 */
struct process* lock_add_process(pid_t pid)
{
    //TODO 1.zablokuj modyfikowanie listy procesów
    //2.sprawdź czy istnieje już taki proces
    struct process *tmp = NULL;
    int fd;
    char process_fifo_name[10];
    if( (tmp = lock_ck_process(pid)) == NULL ){
        tmp = malloc( sizeof(struct process) );
        tmp->pid = pid;
        tmp->next = NULL;
        tmp->prev = NULL;
        tmp->resources = ( struct resource** ) calloc( RESOURCES_TAB_MAX_SIZE, sizeof(struct resource *) );
        tmp->resource_links = 0;

        sprintf(process_fifo_name, "%u", pid);
        if ( (fd = open(process_fifo_name, O_WRONLY)) == -1) {
            err("opening fifo failed: %s", strerror(errno));
            //TODO co się ma stać?
            //dlmd_exit(1);
        }
        tmp->fd = fd;

        if(proc_root != NULL){
            tmp->next = proc_root;
            proc_root->prev = tmp;
        }
        proc_root = tmp;
    }
    return tmp;
    //3.utworz proces i wstaw na początek kolejki
    //TODO 4.odblokuj listę procesów
    //5.zwróc proces
}
/*
 * funkcja dodaje nowy zasob
 */
struct resource* lock_add_resource(struct stat* st)
{
    //TODO 1.zablokuj modyfikowanie listy procesów
    //2.sprawdź czy istnieje już taki proces
    struct resource *tmp = NULL;
    if( (tmp = lock_ck_resource_stat(st)) == NULL ){
        tmp = malloc( sizeof(struct resource) );
        //TODO id zwracane poprzez otwarcie pliku?
        tmp->id = 0;
        tmp->links = 0;
        tmp->locks = NULL;
        tmp->next = NULL;
        tmp->prev = NULL;
        tmp->st_dev = st->st_dev;
        tmp->st_ino = st->st_ino;
        tmp->st_rdev = st->st_rdev;

        if(res_root != NULL){
            tmp->next = res_root;
            res_root->prev = tmp;
        }
        res_root = tmp;
    }
    return tmp;
    //3.utworz proces i wstaw na początek kolejki
    //TODO 4.odblokuj listę procesów
    //5.zwróc proces
}
/*
 * funkcja sprowdza mozliwosc zalozenia blokady zwraca NULL gdy mozna,
 * jezeli nie, zwraca zsaznik na blokade konfliktujaca
 */
struct lock* lock_trylock(struct process *proc, struct resource *res, lock_type_t type)
{
	struct lock *lck = res->locks;
	while (lck->next != NULL) {
		if (!(lck->type & lock_mask[type]) || (lck->proc == proc))
			return lck;
		lck = lck->next;
	}
	return NULL;
}
/*
 * funkcja laczy proces z zasobem
 */
void lock_link_proc_res(struct process *proc, struct resource *res)
{
    int i;
    for(i=0; i < proc->resource_links; ++i)
        if(proc->resources[i]->id == res->id)
            return;
    proc->resources[proc->resource_links] = res;
    ++(proc->resource_links);
    ++(res->links);

}
/*
 * funkcja naklada blokade na zasob, zwraca NULL gdy sie udalo lub wskaznik na blokade
 * konfliktujaca z ta ktora miala byc nalozona
 */
struct lock* lock_add_lock(struct process *proc, struct resource *res, struct lock *lock)
{
	struct lock *lck;
	lck = lock_trylock(proc, res, lock->type);
	if (lck != NULL)
		return lck;
	if (res->locks == NULL)
		res->locks = lock;
	else {
		res->locks->prev = lock;
		lock->next = res->locks;
		res->locks = lock;
	}
	lock->conflict_next = NULL;
	lock->conflict_prev = NULL;
	return NULL;
}
/*
 * 1. dodaje blokade lock do listy blokad oczekujacych (prev, next), ktora jest
 * uporzadkowana od najwczesniej uplywajacego timeoutu (expire)
 * 2. dodaje blokade na koniec listy blokad konfliktujacych (conflict_prev/_next) z blokada lock
 * lista dwukierunkowa cykliczna
 */
int lock_add_waiting_lock(struct lock *lock, struct lock *conflict)
{
	add_conflict_lock(lock, conflict);
	add_waiting_lock(lock);
}
static int add_conflict_lock(struct lock *lock, struct lock *conflict)
{
	if (conflict->conflict_prev == NULL) {
		conflict->conflict_prev = lock;
		conflict->conflict_next = lock;
	} else {
		conflict->conflict_prev->conflict_next = lock;
		lock->conflict_prev = conflict->conflict_prev;
		conflict->conflict_prev = lock;
		lock->conflict_next = conflict;
	}
}
static int add_waiting_lock(struct lock *lock)
{
	struct lock *lck = waiting_root;
	if (waiting_root == NULL)
		waiting_root = lock;
	else {
		if (lock->expire < lck->expire) {
			lock->next = lck;
			lck->prev = lock;
			waiting_root = lock;
			return 0;
		}
		do {
			lck = lck->next;
			if (lock->expire < lck->expire) {
				lock->prev = lck->prev;
				lock->next = lck;
				if (lock->prev != NULL)
					lock->prev->next = lock;
				if (lock->next != NULL)
					lock->next->prev = lock;
				if (lck->prev == NULL)
					waiting_root = lock;
				return;
			}
		} while (lck->next != NULL);
		lck->next = lock;
		lock->prev = lck;
	}
	return 0;
}
/*
 * 1. funkcaja wyjmuje blokade z kolejki blokad oczekujacych gdy skonczyl sie timeout
 * blokady, expire < time.
 * 2. funkcja wyjmuje blokade z listy blokad konfliktujacych
 */
struct lock* lock_get_waiting_lock(time_t time)
{
	if (waiting_root == NULL)
		return NULL;
	if (waiting_root->expire < time) {
		// usun blokade z listy czekajacych
		struct lock *lock = waiting_root;
		waiting_root = lock->next;
		if (lock->next != NULL)
			lock->next->prev = NULL;
		lock->next == NULL;
		lock->prev == NULL;
		// usun blokade z listy konfliktujacych
		rm_waiting_lock(lock);
		return lock;
	}
	return NULL;
}
static void rm_waiting_lock(struct lock *lock)
{
	if (lock->conflict_next == lock->conflict_prev) {
		struct lock *tmp = lock->conflict_next;
		tmp->conflict_next = NULL;
		tmp->conflict_prev = NULL;
	} else {
		lock->conflict_prev->conflict_next = lock->conflict_next;
		lock->conflict_next->conflict_prev = lock->conflict_prev;
	}
	lock->conflict_next = NULL;
	lock->conflict_prev = NULL;
}
/*
 * funkcja usuwa proces i usuwa wszystkie osierocone zasoby
 */
int lock_rm_process(struct process *proc)
{
	struct resource *res;
	int i;
	// usun zasoby jesli potrzeba
	for (i=0; i<proc->resource_links; ++i) {
		--(proc->resources[i]->links);
		if (proc->resources[i]->links == 0)
			res = proc->resources[i];
			if (res->next != NULL)
				res->next->prev = res->prev;
			if (res->prev != NULL)
				res->prev->next = res->next;
			if (res_root == res)
				res_root = NULL;
			free(res);
	}
	free(proc->resources);
	free(proc);
}
/*
 * usuwa blokade z zasobu i zwraca NULL, jezeli sa jakies blokady konfliktujace
 * to proboje je dodac, zwraca wskaznik
 */
struct lock** lock_rm_lock(struct lock *lock, struct resource *res)
{
	// usuwamy z listy blokad
	if (lock->prev != NULL)
		lock->prev->next = lock->next;
	else
		res->locks = lock->next;
	if (lock->next != NULL)
		lock->next->prev = lock->prev;
	struct lock *new = lock->conflict_next;
	struct lock *conflict;
	struct lock *next;
	int i = 0;
	struct lock **added = malloc(16*sizeof(lock*));
	// sprawdzamy kazdy z listy konfliktujacych
	while (new != lock) {
		next = new->conflict_next;
		coflict = lock_trylock(new->proc, res, new->type);
		if (conflict == NULL) {
			rm_waiting_lock(new);
			lock_add_lock(new->proc, res, new);
			if (i == 16)
				added = realloc(added, sizeof(added) + 4);
			added[i] = new;
			++i;
		} else
			add_conflict_lock(new, conflict);
		new = next;
	}
	free (lock);
	return added;
}
